#ifndef __SIMULATOR_H__
#define __SIMULATOR_H__

#include "base/Entity.h"
#include "base/GDB.h"
#include "base/Processor.h"
#include "base/isa_ext.h"
#include "base/simtypes.h"
#include "base/threads/ThreadManager.h"
#include "mem/Memory.h"
#include "util/HashMap.h"
#include "util/List.h"
#include "util/Vector.h"
#include "util/Pool.h"

class CPU;
class SigContext;
class SigManager;
class StatisticsCollection;
class Watcher;

class Simulator {
  public:
    NEW_DELETE_OPERATORS_H(Simulator,NoSuper);
    Simulator (const char *sysName);
    virtual ~Simulator ();

    void run ();
    void stop (int code);
    bool isRunning () {
        return running;
    }

    void activate (Entity*);
    void deactivate (Entity*);
    bool isActive (Entity*);

    Time getSimclock () const {
        return simclock;
    }

    Time getSimclockStats () const {
        return simclockStats;
    }

    int getExitCode () {
        return exitCode;
    }

    Entity* getMemory () {
        return memory;
    }

    Entity* getBottom () {
        return getMemory();
    }

    Entity*          getTop ();

    const char* getSysName () {
        return sysName;
    }

    /** 
        current brk value
    */
    void* _end_data_segment;
    /** 
        return current brk value
     */
    void* getEndDataSegment () {
        return _end_data_segment;
    }
    /** 
        store current brk value after brk syscall or return from real mode
    */
    void setEndDataSegment (void* end_data_segment) {
        _end_data_segment = end_data_segment;
    }

    Processor*       getIdleProc ();
    bool             isHalted (int procID);

    ThreadManager* getTM () {
        return TM;
    }

    SigManager* getSM () {
        return SM;
    }

    Processor*       getProcForSignal ();
    Processor* getProcForThreadSignal (int procID);

    uint getNumCpus () {
        return cfg.num_cpus;
    }

    uint             getNumRunningCpus ();

    virtual uint getMode () {
        return mode;
    }

    bool             isWatched (VA addr);
    virtual void     updateCaches ();
    virtual void     invalidateCaches (Processor* processor, const char* description, VA lb = 0, size_t n = 0);
    virtual void     rawFlushCaches (VA start, size_t len);
    List<Processor*>* verifyCaches ();

    const struct timeval* getStartTime () {
        return &start_time;
    }

    void             quiesce (Entity* e);
    void             unquiesce (Entity* e);
    void             quiesced (Entity* e);
    bool             isQuiesced ();
    bool             isQuiesceActive ();
    List<Entity*>*   quiescedList;
    
    bool             acquireSyscallLock(Entity*);
    void             releaseSyscallLock(Entity*);
    bool             hasSyscallLock(Entity*);
    Entity*          getSyscallLock();

    void noteSignal (SigContext* sigContext, Processor* sigProc);

    void profile ();
    void profileTableOutput ();

    // Statistics
    void statsCountVotes ();
    void printStatistics ();

    StatisticsCollection* getStatistics () {
        return global_statistics; 
    }

    void register_isa_ext (int isa_ext,
                           bool (*function)(Processor* P,
                                            Reg arg0,
                                            Reg arg1,
                                            Reg* result),
                           bool clobberOkay = false);

    bool handle_isa_ext (int isa_ext,
                         Processor* P,
                         Reg arg0,
                         Reg arg1,
                         Reg* result);
    static bool isa_ext_noop (Processor* P, Reg arg0, Reg arg1, Reg* result);
    static bool isa_ext_get_num_cpus (Processor* P,
                                      Reg arg0,
                                      Reg arg1,
                                      Reg* result);
    static bool isa_ext_get_mode (Processor* P,
                                  Reg arg0,
                                  Reg arg1,
                                  Reg* result);
    bool isDone();

    // gdb support
    GDB* getGDB () {
        return gdb;
    }
    bool getRegister (int cpunum, int regnum, size_t len, uint8* val);
    bool putRegister (int cpunum, int regnum, size_t len, uint8* val);
    bool getMemory (int cpunum, VA vAddr, uint nbytes, uint8 *buf);
    bool putMemory (int cpunum, VA vAddr, uint nbytes, uint8 *buf);

    const char* getStatus (int cpunum) {
        return procs[cpunum]->getStatus();
    }

    uint64 getInstructionsExecuted (int cpunum) {
        return procs[cpunum]->getInstructionsExecuted();
    }
    
    Watcher* getWatcher () {
        return watcher;
    }

    struct config {
        uint num_cpus;
        uint blocksize;
        bool stats_enabled;
        bool shutdown_asserts;
        uint watchdog;
        uint proc_watchdog;
        bool profile;
        bool watcherEnabled;
        Time watcherSimclock;
        Reg  watcherAddress;
        bool ipc2;
        bool gdbWait;
        uint gdbPort;
    } cfg;

    static uint getMemLatency (int default_latency = 100);
    // could probably be moved, but for now, Cache needs to know if we have
    // a private L2 from the simulator, and since it is meant to be used
    // in any Simulator, we have this here.
    static bool hasPrivateL2 ();

    VA blockAddr (VA addr) const { 
        return ADDR_UINT32(addr) & ~(VA)(cfg.blocksize - 1); 
    }

    virtual void buildSystem (void* mainX, 
                              int argc,
                              const char** argv,
                              const char** envp);    
    void quiesceEntity (Entity* e);
    uint getNumQuiescingEntities ();

    Histogram* histSTMInstructionsPerTx;
    Histogram* histSTMReadBarriersPerTx;
    Histogram* histSTMWriteBarriersPerTx;
  protected:
    // global statistics
    StatisticsCollection* global_statistics;

    // exit status
    bool running;
    bool done;
    int exitCode;

    // times
    /** 
        simclock is the number of cycles of simulation
     */
    Time simclock;

    /** 
        simclockStats is the number of cycles of simulation while stats_enabled was true
     */
    Time simclockStats;

    /** 
        start_time is used to compute elapsed wall clock time of simulation
     */
    struct timeval start_time;

    // a vector of entities in the system
    // the order the entities are in this array is the order
    // in which they will be ticked
    Vector<Entity*>    entities;
    List<Entity*>      active[CLOCK_SIZE];
    Vector<Processor*> procs;

    // this is for better debugging with gdb. with this, the current execution
    // status of each processor can be easily observed in gdb.
    // note that "procs" or "entities" variables should be used for
    // simulator coding. this is only for debugging.
    Processor** processors;

    ThreadManager* TM;
    SigManager* SM;
    Entity* memory;

    // info for a synchronous signal that happened while ticking a processor
    SigContext* sigContext;
    Processor* sigProc;

    uint numToBeQuiesced;

    void init_isa_ext ();
    bool (*isa_ext_functions[ISA_EXT_MAXIMUM]) (Processor* P,
                                                Reg arg0,
                                                Reg arg1,
                                                Reg* result);

    // This is the name and mode of the system we are simulating
    const char* sysName; 
    uint mode;

    VA watchAddresses[8];
    uint numWatches;
    uint watchMask;
    void getWatchAddresses ();

    Entity* top;

    Entity* profiler;
    HashMap<VA,Time*>** profileTables;
    void profileTableIncrement (Processor* p);
    static int profileTableCompare (const void *a, const void *b);

    Watcher* watcher;

    void* getMainX ();
    void* mainX;

    void buildProcessors (void* mainX, int argc, const char** argv, const char** envp);
    void deleteProcessors ();

    virtual void registerSyscallHooks ();
    virtual PthreadInterface* buildPthreadInterface ();
    virtual Processor* buildProcessor (const char* name, int id);

    virtual void     hookResetQuiesce ();
    virtual void     hookCountQuiesceEntities ();
    virtual void     hookQuiesceEntities ();
    virtual void     hookUnQuiesceEntities ();

    virtual void hookPrintStatistics (FILE* statFile);
    virtual void hookStatsCountVotes ();

    Entity* syscallLock;

    // gdb support
    GDB* gdb;
};

extern Logger*    traceLog;
extern Logger*    quiesceLog;
extern Simulator* global_sim;

#endif // __SIMULATOR_H__
